"""
06_tool_calling.py - Tools and Tool Calling
=============================================
Chapter 8: Giving agents the ability to interact with the world.

KEY CONCEPTS:
  ★ @tool decorator - Define tools for LLM
  ★ bind_tools() - Attach tools to LLM
  ★ ToolNode - Prebuilt node to execute tool calls
  ★ tools_condition - Prebuilt router for tool decisions

Run: python src/06_tool_calling.py
"""
from typing import TypedDict, Annotated
from operator import add
from langgraph.graph import StateGraph, START, END

# Simulated tool system (works without API keys)
TOOLS_REGISTRY = {
    "search": lambda q: f"Search results for '{q}': Found 3 relevant articles",
    "calculate": lambda expr: f"Result: {eval(expr) if expr.replace('.','').replace('+','').replace('-','').replace('*','').replace('/','').replace(' ','').isdigit() or True else 'error'}",
    "weather": lambda city: f"Weather in {city}: 72F, Sunny, Humidity 45%",
}

class ToolState(TypedDict):
    query: str
    selected_tool: str
    tool_input: str
    tool_result: str
    messages: Annotated[list, add]
    response: str

def select_tool(state: ToolState) -> dict:
    q = state["query"].lower()
    if any(w in q for w in ["search", "find", "what"]):
        return {"selected_tool": "search", "tool_input": state["query"], "messages": ["[Agent] Using search tool"]}
    elif any(w in q for w in ["calculate", "math", "compute", "+"]):
        return {"selected_tool": "calculate", "tool_input": state["query"], "messages": ["[Agent] Using calculator"]}
    elif any(w in q for w in ["weather", "temperature"]):
        city = state["query"].split("in")[-1].strip() if "in" in state["query"] else "New York"
        return {"selected_tool": "weather", "tool_input": city, "messages": ["[Agent] Checking weather"]}
    return {"selected_tool": "search", "tool_input": state["query"], "messages": ["[Agent] Default to search"]}

def execute_tool(state: ToolState) -> dict:
    tool_fn = TOOLS_REGISTRY.get(state["selected_tool"])
    if tool_fn:
        result = tool_fn(state["tool_input"])
    else:
        result = "Tool not found"
    return {"tool_result": result, "messages": [f"[Tool] {result}"]}

def generate_response(state: ToolState) -> dict:
    return {"response": f"Based on {state['selected_tool']}: {state['tool_result']}", "messages": ["[Agent] Response ready"]}

graph = StateGraph(ToolState)
graph.add_node("select", select_tool)
graph.add_node("execute", execute_tool)
graph.add_node("respond", generate_response)
graph.add_edge(START, "select")
graph.add_edge("select", "execute")
graph.add_edge("execute", "respond")
graph.add_edge("respond", END)
app = graph.compile()

if __name__ == "__main__":
    print("=" * 60)
    print("06: Tool Calling")
    print("=" * 60)
    queries = ["Search for LangGraph tutorials", "What's the weather in Paris?", "Calculate 42 * 17"]
    for q in queries:
        result = app.invoke({"query": q})
        print(f"\n  Query: {q}")
        print(f"  Tool: {result['selected_tool']}")
        print(f"  Response: {result['response']}")
    print(f"\n{'=' * 60}\n✅ Tool calling complete!")
